#!/usr/bin/env python
#
# Copyright 2013 Fabio Viola
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import sys
sys.path.append('/usr/share/inkscape/extensions')
import inkex, simplestyle
from simplestyle import *
import random
import re
import gettext

class Crossword(inkex.Effect):

    """
    Creates a crossword.
    """

    def __init__(self):
        """
        Constructor. Initializes variables.
        """

        inkex.Effect.__init__(self)
        self.OptionParser.add_option('-w', '--width', action='store', type='string', dest='width', default='25', help="This is the number of cells for each row")
        self.OptionParser.add_option('-y', '--height', action='store', type='string', dest='height', default='15', help="This is the number of cells for each col")
        self.OptionParser.add_option('--helpletters', action='store', type='string', dest='helpletters', default='', help="Here you can specify help letters to be inserted into the crossword. Format: row,column,letter;")
        self.OptionParser.add_option('--blackcells', action='store', type='string', dest='blackcells', default='', help="Specify here the position of the black cells")
        self.OptionParser.add_option('--randblackcells', action='store', type='inkbool', dest='randblackcells', default='false', help="Check this one if you want randomly positioned black cells. This overwrites the previous field.")
        self.OptionParser.add_option('--syllabic', action='store', type='inkbool', dest='syllabic', default='false', help="Check this one if you want large cells")
        self.OptionParser.add_option('--numbered', action='store', type='inkbool', dest='numbered', default='true', help="Check this one if you want the cells to be numbered")
        self.OptionParser.add_option('--withoutschema', action='store', type='inkbool', dest='withoutschema', default='false', help="Check this one if you want a no schema crossword (no black cells)")


    def effect(self):
        """
        Creates a crossword grid.
        """
        
        h = self.options.height
        w = self.options.width
        
        # get svg data
        svg = self.document.getroot()
                
        # create a layer
        layer = inkex.etree.SubElement(svg, 'g')
        layer.set(inkex.addNS('label', 'inkscape'), 'Crossword')
        layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')

        # determine the cell width
        if self.options.syllabic:
            cell_width = 50
        else:
            cell_width = 25
        cell_height = 25
     
        # create a matrix
        # each cell is a dictionary with 'white' and 'numbered' fields.
        cell = []
        for i in range(int(h)):
            cell.append([])
            for j in range(int(w)):
                cell[i].append({'white':1, 'numbered':0})
   
        # now we determine which cells will be black
        if not(self.options.withoutschema):
            # check if the users gave us the black cells position
            if not(self.options.randblackcells):

                ### we use regexp to prevent errors during live preview                
                bcarray = self.options.blackcells.split(";")
                p = re.compile("[0-9][0-9]*[,][0-9][0-9]*")
                for bc in bcarray:   
                    m = p.match(bc)
                    if m:
                        pos = bc.split(",")
                        if (int(pos[0])-1 < int(h)) and (int(pos[1])-1 < int(w)):
                            cell[int(pos[0])-1][int(pos[1])-1]['white'] = 0
            else:
            # otherwise we select a random number of black cells per line
                for i in range(int(h)):
                    black_cells_limit = random.randint(0, int(w)/2)
                    for j in range(black_cells_limit):
                        k = random.randint(0, int(w)-1)
                        cell[i][k]['white'] = 0
                        random.shuffle(cell[i])

        # if it's not a "no schema crossword", we have to find isolated white cells
        # and make them black
        if not(self.options.withoutschema):

            ### CORNERS

            # top left corner
            if (cell[0][0]['white']) and not(cell[0][1]['white']) and not(cell[1][0]['white']):
                cell[0][0]['white'] = 0
                
            # top right corner
            if (cell[0][int(w)-1]['white']) and not(cell[0][int(w)-2]['white']) and not(cell[1][int(w)-1]['white']):
                cell[0][int(w)-1]['white'] = 0

            # bottom left corner
            if (cell[int(h)-1][0]['white']) and not(cell[int(h)-2][0]['white']) and not(cell[int(h)-1][1]['white']):
                cell[int(h)-1][0]['white'] = 0

            # bottom right corner
            if (cell[int(h)-1][int(w)-1]['white']) and not(cell[int(h)-1][int(w)-2]['white']) and not(cell[int(h)-2][int(w)-1]['white']):
                cell[int(h)-1][int(w)-1]['white'] = 0

            ### BORDERS

            # top and bottom borders
            for j in range(1, int(w)-1):

                if cell[0][j]['white'] and not(cell[0][j-1]['white']) and not(cell[0][j+1]['white']) and not(cell[1][j]['white']):
                    cell[0][j]['white'] = 0

                if cell[int(h)-1][j]['white'] and not(cell[int(h)-1][j-1]['white']) and not(cell[int(h)-1][j+1]['white']) and not(cell[int(h)-2][j]['white']):
                    cell[int(h)-1][j]['white'] = 0

            # left and right borders
            for i in range(1, int(h)-1):

                if cell[i][0]['white'] and not(cell[i-1][0]['white']) and not(cell[i+1][0]['white']) and not(cell[i][1]['white']):
                    cell[i][0]['white'] = 0

                if cell[i][int(w)-1]['white'] and not(cell[i-1][int(w)-1]['white']) and not(cell[i+1][int(w)-1]['white']) and not(cell[i][int(w)-2]['white']):
                    cell[i][int(w)-1]['white'] = 0

            ### INNER CELLS

            # inner cells
            for i in range(1, int(h)-1):
                for j in range(1, int(w)-1):
                    if (cell[i][j]['white']) and not(cell[i-1][j]['white']) and not(cell[i+1][j]['white']) and not(cell[i][j-1]['white']) and not(cell[i][j+1]['white']):
                        cell[i][j]['white'] = 0            

        ################################################################
        #
        # Drawing rectangles!
        #
        ################################################################

        whitestyle = {'stroke':'#000000', 'fill':'#ffffff'}
        blackstyle = {'stroke':'#000000', 'fill':'#000000'}
        for i in range(int(h)):
            for j in range(int(w)):
                if cell[i][j]['white']:
                    square_attribs = {
                        'style': simplestyle.formatStyle(whitestyle),
                        'height': str(cell_height),
                        'width': str(cell_width),
                        'x': str(j*cell_width+25),
                        'y': str(i*cell_height+25)
                        }
                else:
                    square_attribs = {
                        'style': simplestyle.formatStyle(blackstyle),
                        'height': str(cell_height),
                        'width': str(cell_width),
                        'x': str(j*cell_width+25),
                        'y': str(i*cell_height+25)
                        }                    
                shape = inkex.etree.Element(inkex.addNS('rect','svg'), square_attribs)
                layer.append(shape)                    


        ################################################################
        #
        # Drawing help letters!
        #
        ################################################################

        if len(self.options.helpletters) > 0:
            letters = self.options.helpletters.split(";")
            letters_style = {'text-align': 'center', 'fill': '#000000', 'stroke': 'none', 'font-size':20}
            for l in letters:
                # check format
                if self.options.syllabic:
                    p = re.compile("[0-9]+[,][0-9]+[,][a-zA-Z]{1,4}")
                    offset = 35
                else:
                    p = re.compile("[0-9]+[,][0-9]+[,][a-zA-Z]{1}")
                    offset = 31
                if p.match(l):
                    l1 = l.split(",")
                    if (int(l1[0])-1 < int(h)) and (int(l1[1])-1 < int(w)):
                        letterlabel_attribs = {
                            'x': str(offset + (int(l1[1])-1) * cell_width),
                            'y': str(20 + int(l1[0]) * cell_height),
                            'style': formatStyle(letters_style),
                            }                
                        letterlabel = inkex.etree.Element(inkex.addNS('text','svg'), letterlabel_attribs)                    
                        if self.options.syllabic:
                            if len(l1[2]) > 4:
                                letterlabel.text = l1[2][0:4]
                            else:
                                letterlabel.text = l1[2]
                        else:
                            letterlabel.text = l1[2][0]
                            layer.append(letterlabel)

        ################################################################
        #
        # Drawing numbers!
        #
        ################################################################
                    
        # if the crossword type is with a schema, we have to determine 
        # which cells must be numbered
        if self.options.numbered and not(self.options.withoutschema):
        
            # horizontal scan
            for i in range(int(h)):
                for j in range(int(w)):
                    # searching words in each line. 
                    # the current cell starts a word if the previous cell is black or an edge and
                    # the following cell is not black or an edge
                    if cell[i][j]['white'] and self.check_left_el(cell, i,j) and not(self.check_right_el(cell, i,j)):
                        cell[i][j]['numbered'] = 1

            # vertical scan
            for j in range(int(w)):
                for i in range(int(h)):
                    # searching words in each vertical line. 
                    # the current cell starts a word if the previous cell is black or an edge and
                    # the following cell is not black or an edge
                    if cell[i][j]['white'] and self.check_up_el(cell, i,j) and not(self.check_down_el(cell, i,j)):
                        cell[i][j]['numbered'] = 1

            # draw the numbers
            numlabel_style = {'text-align': 'center', 'fill': '#000000', 'stroke': 'none', 'font-size':8}
            current = 0
            for i in range(int(h)):
                for j in range(int(w)):
                    if cell[i][j]['numbered']:
                        numlabel_attribs = {
                            'x': str(27+cell_width*j),
                            'y': str(33+cell_height*i),
                            'style': formatStyle(numlabel_style),
                            }
                        numlabel = inkex.etree.Element(inkex.addNS('text','svg'), numlabel_attribs)                    
                        current+=1
                        numlabel.text = str(current)
                        layer.append(numlabel)

        # otherwise, if it's a "no schema crossword", we only put numbers on the top
        # and left sides
        elif self.options.numbered and self.options.withoutschema:

            numlabel_style = {'text-align': 'center', 'fill': '#000000', 'stroke': 'none'}  
            for i in range(int(w)):                    
                numlabel_attribs = {
                    'x': str(25+cell_width*i),
                    'y': '20',
                    'style': formatStyle(numlabel_style),
                    }
                numlabel = inkex.etree.Element(inkex.addNS('text','svg'), numlabel_attribs)
                numlabel.text = str(i+1)
                layer.append(numlabel)

            for j in range(int(h)):
                numlabel_style = {'text-align': 'center', 'fill': '#000000', 'stroke': 'none'}
                numlabel_attribs = {
                    'x': '0',
                    'y': str(20+25*(j+1)),
                    'style': formatStyle(numlabel_style)
                    }
                numlabel = inkex.etree.Element(inkex.addNS('text','svg'), numlabel_attribs)
                numlabel.text = str(j+1)
                layer.append(numlabel)      


        ################################################################
        #
        # Drawing a large edge!
        #
        ################################################################

        style = {'stroke':'#000000', 'stroke-width': '2', 'fill':'none'}
        square_attribs = {
            'style': simplestyle.formatStyle(style),
            'height': str(cell_height * int(h)),
            'width': str(cell_width * int(w)),
            'x': '25',
            'y': '25'
            }
        shape = inkex.etree.Element(inkex.addNS('rect','svg'), square_attribs)
        layer.append(shape)


# useful functions...

    def check_left_el(self, cell, i, j):
        """
        Returns true if che cell i,j is on the left border, or if it has a black cells on his left
        """
        if (j == 0) or ((j > 0) and (not(cell[i][j-1]['white']))):
            return True
        else:
            return False
        
    def check_right_el(self, cell, i, j):
        """
        Returns true if che cell i,j is on the right border, or if it has a black cells on his right
        """
        if (j == int(self.options.width)-1) or ((j < int(self.options.width)-1) and (not(cell[i][j+1]['white']))):
            return True
        else:
            return False

    def check_up_el(self, cell, i, j):
        """
        Returns true if che cell i,j is on the left border, or if it has a black cells on his left
        """
        if (i == 0) or ((i > 0) and (not(cell[i-1][j]['white']))):
            return True
        else:
            return False
        
    def check_down_el(self, cell, i, j):
        """
        Returns true if che cell i,j is on the right border, or if it has a black cells on his right
        """
        if (i == int(self.options.height)-1) or ((i < int(self.options.height)-1) and (not(cell[i+1][j]['white']))):
            return True
        else:
            return False


# Let's the magic begin!        

effect = Crossword()
effect.affect()
